/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package mwt.xml.xdbforms.xformlayer.transactions.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import mwt.xml.xdbforms.cache.CacheService;
import mwt.xml.xdbforms.cache.CacheServiceFactory;
import mwt.xml.xdbforms.cache.exception.CacheServiceException;
import mwt.xml.xdbforms.dblayer.ConnectionFactory;
import mwt.xml.xdbforms.schemalayer.SchemaDocument;
import mwt.xml.xdbforms.schemalayer.SchemaDocumentBuilder;
import mwt.xml.xdbforms.schemalayer.SchemaDocumentBuilderFactory;
import mwt.xml.xdbforms.schemalayer.exception.SchemaBuildingException;
import mwt.xml.xdbforms.schemalayer.exception.SchemaDocumentBuilderException;
import mwt.xml.xdbforms.xformlayer.XFormDocument;
import mwt.xml.xdbforms.xformlayer.XFormDocumentBuilder;
import mwt.xml.xdbforms.xformlayer.XFormDocumentBuilderFactory;
import mwt.xml.xdbforms.xformlayer.exception.XFormDocumentException;
import mwt.xml.xdbforms.xformlayer.transactions.CommitterService;
import mwt.xml.xdbforms.xformlayer.transactions.XFormTransaction;
import mwt.xml.xdbforms.xformlayer.transactions.exception.CommitterServiceCommitEx;
import mwt.xml.xdbforms.xformlayer.transactions.exception.CommitterServiceValidateEx;
import mwt.xml.xdbforms.xformlayer.transactions.exception.XFormTransactionException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Progetto Master Web Technology
 * @author Gianfranco Murador, Cristian Castiglia, Matteo Ferri
 */
public class XFormInsert implements XFormTransaction {

    //   private ConnectionFactory cf;
    //   la connection factory deve essere settata dall'ambiente
    private SchemaDocument sd;
    private XFormDocument xfd;
    private SchemaDocumentBuilderFactory schemaFactory;
    private XFormDocumentBuilderFactory xformFactory;
    private CacheService xformCache;
    private CacheService schemaCache;
    private CacheService dataInstanceCache;
    private String database;
    private String table;
    private Map<String, String> parameters;

    public XFormInsert(String database) {

        schemaFactory = SchemaDocumentBuilderFactory.newInstance();
        xformFactory = XFormDocumentBuilderFactory.newInstance();
        ConnectionFactory.getInstance().setDatabase(database);
        this.database = database;
        CacheServiceFactory csf = CacheServiceFactory.newInstance();
        xformCache = csf.newXFormCacheService();
        schemaCache = csf.newXSchemaCacheService();
        dataInstanceCache = csf.newDataInstanceCacheService();

    }

    public void require(String table, Submission s, Object[] keys) throws XFormTransactionException {


        SchemaDocumentBuilder schemaBuilder = schemaFactory.newSchemaDocumentBuilder();
        XFormDocumentBuilder xformBuilder = xformFactory.newXFormDocumentBuilder();

        parameters = new HashMap<String, String>();
        parameters.putAll(s.toHashMap());
        this.table = table;

        Map<String, String> nsBindings = new HashMap<String, String>();
        nsBindings.put("xs", XMLConstants.W3C_XML_SCHEMA_NS_URI);

        try {
            schemaBuilder.addNameSpaceBinding(nsBindings);
            sd = schemaBuilder.newSchemaDocument(table, database);
            xfd = xformBuilder.newXFormDocument();
            xfd.buildDataModel(sd, s.getSchemaURL().toString());

        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (SchemaDocumentBuilderException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        }

    //  SchemaDocument sd = builder.newSchemaDocument(table, cmd);

    }

    /**
     * Per una insert non sono importanti le chiavi primarie.
     * @param table
     * @param s
     * @throws mwt.xml.xdbforms.xformlayer.transactions.exception.XFormTransactionException
     */
    public void require(String table, Submission s) throws XFormTransactionException {

        require(table, s, null);

    }

    /**
     * Definire un validatore dell'input
     * @param inputStream
     * @throws mwt.xml.xdbforms.xformlayer.transactions.exception.XFormTransactionException
     */
    public void commit(InputStream inputStream) throws XFormTransactionException {
        try {
            CommitterService cs = new CommitterServiceImpl();
            cs.validate(sd, inputStream);
            cs.insert(table);
        } catch (CommitterServiceCommitEx ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (CommitterServiceValidateEx ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        }


    }

    public String getDocument(OutputStream os) throws XFormTransactionException {

        ByteArrayOutputStream baos;

        try {

            if (xformCache.contains(database + "_" + table + "_insert")) {

                baos = (ByteArrayOutputStream) xformCache.getLastObject(database + "_" + table + "_insert");

            } else {

                baos = new ByteArrayOutputStream();
                xfd.buildPresentation(parameters, baos);
                xformCache.load(baos, database + "_" + table + "_insert");
            }
            //baos.writeTo(os);
            return baos.toString();

        /*} catch (IOException ex) {
        throw new XFormTransactionException(ex.getMessage(), ex);*/
        } catch (CacheServiceException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException("getDocument(): " + ex.getMessage(), ex);
        }

    }

    public String getSchemasBinding(OutputStream os) throws XFormTransactionException {
        try {

            Document document;

            if (schemaCache.contains(database + "_" + table + "_insert")) {

                document = (Document) schemaCache.getLastObject(database + "_" + table + "_insert");

            } else {
                document = sd.getSchemaDocument();
                schemaCache.load(document, database + "_" + table + "_insert");
            }
            DOMImplementationLS domimpl = (DOMImplementationLS) document.getImplementation();
            LSOutput lso = domimpl.createLSOutput();
            LSSerializer lss = domimpl.createLSSerializer();
            lso.setEncoding("UTF-8");
            Writer w = new OutputStreamWriter(os);
            lso.setCharacterStream(w);
            lss.getDomConfig().setParameter("format-pretty-print", true);
            lss.write(document, lso);

            return toString(document);
        } catch (CacheServiceException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (SchemaBuildingException ex) {
            throw new XFormTransactionException("getSchemasBinding() " + ex.getMessage(), ex);
        }


    }

    public String getDataInstance(OutputStream os) throws XFormTransactionException {
        try {
            Document document;

            if (dataInstanceCache.contains(database + "_" + table + "_insert")) {
                document = (Document) dataInstanceCache.getLastObject(database + "_" + table + "_insert");

            } else {

                document = xfd.getDataModel();
                dataInstanceCache.load(document, database + "_" + table + "_insert");

            }
            DOMImplementationLS domimpl = (DOMImplementationLS) document.getImplementation();
            LSOutput lso = domimpl.createLSOutput();
            LSSerializer lss = domimpl.createLSSerializer();
            lso.setEncoding("UTF-8");
            Writer w = new OutputStreamWriter(os);
            lso.setCharacterStream(w);
            lss.getDomConfig().setParameter("format-pretty-print", true);
            lss.write(document, lso);

            return toString(document);

        } catch (CacheServiceException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException("getDataInstance() " + ex.getMessage(), ex);
        }



    }

    public String getValuesInstance(OutputStream os) throws XFormTransactionException {
        try {
            Document document = null;
            if (dataInstanceCache.contains(database + "_" + table + "_insert_val")) {
                document = (Document) dataInstanceCache.getLastObject(database + "_" + table + "_insert_val");
            } else {
                if (xfd.getValueModel() != null) {
                    document = xfd.getValueModel();
                    dataInstanceCache.load(document, database + "_" + table + "_insert_val");
                }
            }

            if (document != null) {
                DOMImplementationLS domimpl = (DOMImplementationLS) document.getImplementation();
                LSOutput lso = domimpl.createLSOutput();
                LSSerializer lss = domimpl.createLSSerializer();
                lso.setEncoding("UTF-8");
                Writer w = new OutputStreamWriter(os);
                lso.setCharacterStream(w);
                lss.getDomConfig().setParameter("format-pretty-print", true);
                lss.write(document, lso);
                return toString(document);
            }

            return null;

        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (CacheServiceException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        }


    }

    public String getMergedDocument() throws XFormTransactionException {

        Document xform = null;

        try {

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            if (!xformCache.contains(database + "_" + table + "_insert")) {

                Document dataModel = xfd.getDataModel();
                Document valuesModel = xfd.getValueModel();


                String docStr = getDocument(null);
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                dbf.setValidating(false);
                dbf.setNamespaceAware(true);

                InputSource inputSource = new InputSource();
                inputSource.setCharacterStream(new StringReader(docStr));
                DocumentBuilder builder = dbf.newDocumentBuilder();
                xform = builder.parse(inputSource);

                NodeList nl = xform.getElementsByTagName("xf:instance");

                for (int i = 0; i < nl.getLength(); i++) {
                    nl.item(i).getAttributes().removeNamedItem("src");
                    Element e = (Element) nl.item(i);
                    e.setAttribute("xmlns", "");
                }

                Node n = nl.item(0);
                Node impNode = xform.importNode(valuesModel.getDocumentElement(), true);
                n.appendChild(impNode);
                n = nl.item(1);
                impNode = xform.importNode(dataModel.getDocumentElement(), true);
                n.appendChild(impNode);
                xformCache.load(baos, database + "_" + table + "_insert");
                
                
                DOMImplementationLS domimpl = (DOMImplementationLS) xform.getImplementation();
                LSOutput lso = domimpl.createLSOutput();
                LSSerializer lss = domimpl.createLSSerializer();
                lso.setEncoding("UTF-8");
                Writer w = new OutputStreamWriter(baos);
                lso.setCharacterStream(w);
                lss.getDomConfig().setParameter("format-pretty-print", true);
                lss.write(xform, lso);


            } else {

                baos = (ByteArrayOutputStream) xformCache.getLastObject(database + "_" + table + "_insert");

            }



            return baos.toString();

        // Riprendo il document xform
        } catch (SAXException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (IOException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (ParserConfigurationException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (Exception e) {
            throw new XFormTransactionException(e.getMessage(), e);
        }


    // Riprendo il document xform

    }

    private String toString(Document d) {

        try {
            DOMSource domSource = new DOMSource(d);
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.transform(domSource, result);
            return writer.toString();
        } catch (TransformerException ex) {
            ex.printStackTrace();
            return null;
        }

    }
}
